#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/wait.h>

//--------------My buffer-------------------------------------------

#define MY_BUFFER_SIZE		1024
static unsigned int my_buffer_len; //length my buffer
static char buffer[MY_BUFFER_SIZE]; //my buffer
static unsigned int my_buffer_pos; //position from which to continue reading

//--------------Waiting---------------------------------------------

static wait_queue_head_t	read_write_my_buffer;
#define	READ_TIMEOUT		15000 //waiting time

//--------------Read from /proc-------------------------------------

static struct proc_dir_entry	*proc_read;

static unsigned int *update_position(loff_t *pos)
{
	int err = 0; //variable displays an error

	if (*pos && !my_buffer_len)
		return NULL;//There is nothing to read already and no time to wait
	
	if (*pos >= my_buffer_len) {//This is end of my buffer
		my_buffer_pos = 0;
		*pos -= my_buffer_len;
		my_buffer_len = 0;
		wake_up_interruptible(&read_write_my_buffer);
	}
	//Waiting to put data in buffer, if it is empty
	err = wait_event_interruptible_timeout(read_write_my_buffer, (my_buffer_len != 0),READ_TIMEOUT);
	
	if (err == 0) {//Timeout end
		printk("Timeout end\n");
		return NULL;
	}
	
	if (!err)
		return ERR_PTR(err);//Was error
	
	if (my_buffer_pos && !*pos)
		*pos = ++my_buffer_pos;//Read from the last position
	
	if (*pos != my_buffer_pos)
		my_buffer_pos = *pos;//Remember the current position

//	printk("update_position\n");
	return &my_buffer_pos;
}

//seq_file interface
static void *read_seq_start(struct seq_file *sf, loff_t *pos)
{
	return update_position(pos);
}
static void *read_seq_next(struct seq_file *sf, void *v, loff_t *pos)
{
	(*pos)++;
	return update_position(pos);
}
static void read_seq_stop(struct seq_file *sf, void *p) {	}
static int read_seq_show(struct seq_file *sf, void *p)
{
	return seq_putc(sf, buffer[*((unsigned int *)p)]);
}

//seq_file operations
static const struct seq_operations read_seq_ops = {
	.start		= read_seq_start,
	.next		= read_seq_next,
	.show		= read_seq_show,
	.stop		= read_seq_stop
};

static int read_my_pipe_open(struct inode *inode, struct file *file)
{//Open buffer for reading
	//Open a file for reading by the operation seq_file
	printk("read_my_pipe_open\n");
	return seq_open(file, &read_seq_ops);
}

static ssize_t read_my_pipe_read(struct file *filp, char __user *buf, size_t size,loff_t *off)
{//Read from buffer
	struct seq_file *sf = (struct seq_file *)filp->private_data;

	//Allocate buffer with requested block size
	sf->buf = kmalloc((sf->size = size + 1), GFP_KERNEL);


	printk("read_my_pipe_open\n");
	return seq_read(filp, buf, size, off);
}

//file operations
static const struct file_operations read_fops = {
	.owner		= THIS_MODULE,
	.open		= read_my_pipe_open,
	.read		= read_my_pipe_read,
	.release	= seq_release
};

//--------------Write in /proc--------------------------------------

static struct proc_dir_entry	*proc_write;

static ssize_t write_my_pipe_write(struct file *file, const char __user *data, size_t len, loff_t *offset)
{//Write data in my buffer
	int write_bytes = 0;
	unsigned long failed_bytes = 0;

	//Waiting to clean that damn buffer
	wake_up_interruptible(&read_write_my_buffer);
	wait_event_interruptible(read_write_my_buffer, (my_buffer_len == 0));
	//Clean buffer
	memset(buffer, 0x00, MY_BUFFER_SIZE);
	//Read as much as possible
	write_bytes = (len > MY_BUFFER_SIZE) ? MY_BUFFER_SIZE : len;
	failed_bytes = copy_from_user(&buffer[my_buffer_len], data, write_bytes);
	if (failed_bytes) {
		printk("Error writting\n");
		return -EFAULT;
	}
	my_buffer_len += write_bytes;
	*offset += write_bytes;

	printk("write_my_pipe_write\n");
	return write_bytes;
}

static int write_my_pipe_release(struct inode *inode, struct file *file)
{//Release buffer for what it's worth
	//Wake up reading process
	wake_up_interruptible(&read_write_my_buffer);

	printk("write_my_pipe_release\n");
	return 0;
}

//file operations
static const struct file_operations write_fops = {
	.owner		= THIS_MODULE,
	.write		= write_my_pipe_write,
	.release	= write_my_pipe_release
};

//--------------Module init/exit------------------------------------

static int __init init_my_pipe(void)
{
	my_buffer_len = 0;
	my_buffer_pos = 0;
	//Create /proc/read
	proc_read = proc_create("read", 0444, NULL, &read_fops);
	if (!proc_read) {
		printk("Error in proc_create for /proc/read\n");
		return -EFAULT;
	}
	//Create /proc/write
	proc_write = proc_create("write", 0222, NULL, &write_fops);
	if (!proc_write) {
		printk("Error in proc_create for /proc/write\n");
		return -EFAULT;
	}
	//Init waiting
	init_waitqueue_head(&read_write_my_buffer);

	printk("my_pipe_init\n");
	return 0;
}

static void __exit exit_my_pipe(void)
{
	//Remove /proc/ files 
	remove_proc_entry("read", NULL);
	remove_proc_entry("write", NULL);

	printk("my_pipe_exit\n");
}

module_init(init_my_pipe)
module_exit(exit_my_pipe)

//--------------Information about module----------------------------

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anastasiya Solomina <sterlyadka333@mail.ru>");
